import copy
import itertools
import random
import sys
import time
import Distribution
from Distribution import CheckRequiredDistribution
from ExceptionHandler import NoAvailableRoomTimeException

'''种群初始化(随机性) spf'''


# 随机数种子
# random.seed(1234)
def Load(tasks, task_room, task_time, classrooms, classroom_unavailable_time, pop, distribution_required):
    schemesList = []  # 保存排课方案的总列表
    classroomsList = []
    conflictsList = []

    for i in range(pop):
        t1 = time.time()
        try:
            while True:
                schemeList, classroomList, conflictList = generate_schedule(tasks, task_room, task_time, classrooms,
                                                                        classroom_unavailable_time,
                                                                        distribution_required)
                if CheckRequiredDistribution(schemeList, distribution_required):
                    break
            schemesList.append(schemeList)
            classroomsList.append(classroomList)
            conflictsList.append(conflictList)
            t2 = time.time()
            print(f"第{i}个方案生成成功。用时：{t2 - t1}")
        except NoAvailableRoomTimeException as e:
            print(e)
            sys.exit(1)
    return schemesList, classroomsList, conflictsList


'''给教学任务随机选择合适的教室时间片  SPF  2024.9.4'''
'''先选时间，再选教室'''


def generate_schedule(tasks, task_room, task_time, classrooms, classroom_unavailable_time, distribution_required):
    # 复制教学任务列表以避免修改原始数据
    tasks_copy = []
    # 记录有冲突的教学任务列表
    tasks_conflict = []
    # 复制教室列表
    classrooms_copy = copy.deepcopy(classrooms)

    # 教学任务添加教室时间笛卡尔积后
    # 根据笛卡尔积长度升序排序（避免后面出现教室冲突的问题）
    tasks_rooms_times = task_room_time(tasks, task_room, task_time)
    tasks_sorted = sorted(tasks_rooms_times, key=lambda x: len(x[-1]))

    # 设置随机数种子调试
    # np.random.seed(1234)

    # 遍历每门课程并分配教室和时间段
    for i, task in enumerate(tasks_sorted):
        # 先找到可用时间
        class_id = task[0][2]
        # 组成笛卡尔积
        room_tims = task[-1]
        # 去掉不可用教室时间组合
        if task[0][4]:
            for room_time in room_tims[:]:
                for un_room in classroom_unavailable_time:
                    if room_time[0][0] == un_room[0]:
                        for day, start, length, week in un_room[1]:
                            if ((int(week, 2) & int(room_time[1][3], 2) == 0) or (
                                    int(day, 2) & int(room_time[1][0], 2) == 0)):
                                continue
                            # 计算选择的时间和教室时间是否有重叠
                            exist_end = start + length
                            new_end = room_time[1][1] + room_time[1][2]
                            if (start < new_end and exist_end > room_time[1][1]):
                                # 删除教室时间片组合如果长度大于1的话
                                if room_time in room_tims:
                                    room_tims.remove(room_time)
                                # room_tims.remove(room_time)
                        break
                break

        room_tim = None

        # 如果时间为空时一直循环，有的教学任务是不要安排教室的
        while (room_tim is None):
            # 如果教学任务中的room字段是false时，不要分配教室
            if not task[0][4]:
                # 随机选择一个教室时间片
                # 找到最小的值
                min_value0 = min(x[1][4] for x in room_tims)

                # 从具有最小值的元素中随机选择一个
                min_elements0 = [x for x in room_tims if x[1][4] == min_value0]
                room_tim = random.choice(min_elements0)
                tasks_copy.append(task[0] + (((0, 0), room_tim[1]), 0))
                break

            # 随机选择一个教室时间片
            room_tim = random.choice(room_tims)
            # 如果需要教室，就判断教室时间片是否冲突
            isConflit = Conflit(room_tim[0][0], room_tim[1], classrooms_copy)

            room_tims_copy = room_tims
            # 如果有冲突，就把时间时间置为空
            if isConflit:
                # 删除教室时间片组合如果长度大于1的话
                if len(room_tims) > 1:
                    room_tims.remove(room_tim)
                    room_tim = None
                    continue
                # 随机选择一个教室时间片
                room_tim = random.choice(room_tims)
                # 如果需要教室，就判断教室时间片是否冲突
                isConflit1 = Conflit(room_tim[0][0], room_tim[1], classrooms_copy)
                if isConflit1:
                    # 找到最小的值
                    min_value = min(x[0][1] + x[1][4] for x in room_tims_copy)

                    # 从具有最小值的元素中随机选择一个
                    min_elements = [x for x in room_tims_copy if x[0][1] + x[1][4] == min_value]
                    room_tim = random.choice(min_elements)
                    # room_tim = min(room_tims_copy, key=lambda x: x[0][1] + x[1][4])
                    # room_tim = random.choice(room_tims_copy)
                # 不冲突的话就把时间加到教室列表后面
                for i, room in enumerate(classrooms_copy[:]):
                    if room[0] == room_tim[0][0]:
                        classrooms_copy[i] = room + ((room_tim[1], task[0][2]),)
                        break

                # 教学任务后面加教室(带惩罚值)和时间
                tasks_copy.append(task[0] + (room_tim, 1))
                # 记录在教学任务冲突表中
                tasks_conflict.append(task[0] + room_tim)
                continue

            # 不冲突的话就把时间加到教室列表后面
            for i, room in enumerate(classrooms_copy[:]):
                if room[0] == room_tim[0][0]:
                    classrooms_copy[i] = room + ((room_tim[1], task[0][2]),)
                    break

            # 教学任务后面加教室(带惩罚值)和时间
            tasks_copy.append(task[0] + (room_tim, 0))

    return tasks_copy, classrooms_copy, tasks_conflict


'''检验教室时间冲突 SPF 2024.9.5'''


def Conflit(rid, tim, classroom):
    room_generator = (room for room in classroom if room[0] == rid)
    room = next(room_generator)
    if len(room) == 2:
        return False
    # for day, start, length, week, _ in room[2:]:
    for rom in room[2:]:
        day, start, length, week = rom[0][0], rom[0][1], rom[0][2], rom[0][3]
        # 如果教师分配的周和天数不一样就说明室不冲突的
        if ((int(week, 2) & int(tim[3], 2) == 0) or (int(day, 2) & int(tim[0], 2) == 0)):
            continue
        # 计算选择的时间和教室时间是否有重叠
        exist_end = start + length
        new_end = tim[1] + tim[2]
        if (start < new_end and exist_end > tim[1]):
            return True
    return False


# def Container(task, classrooms, room):
#     for classroom in classrooms:
#         if classroom[0] == room[0]:
#             if classroom[1] >= task[3]:
#                 return True
#
#     return False


# 教学任务教室时间的关联
def task_room_time(tasks, task_room, task_time):
    t_room_times = []
    for task in tasks:
        rooms = []
        if not task[4]:
            rooms = [(0, 0)]
        else:
            for room in task_room:
                if task[2] == room[0]:
                    rooms = room[1]
                    break
        times = []
        for time in task_time:
            times = []
            if task[2] == time[0]:
                times = time[1]
                break
        # 教学任务添加教室时间片组成笛卡尔积
        t_room_times.append([task, list(itertools.product(rooms, times))])
    return t_room_times
